Solution: Specify Values in Data

Let's solve the antipattern 31 Flavors by specifying values in the data.

There’s a better solution to restrict values in a column: creating a lookup table with one row for each value that we allow in the Bugs.status column and then declaring a foreign key constraint on Bugs.status to reference the new table.

Creating Bugs table using the status

When we insert or update a row in the Bugs table, we must use a status value that exists in the BugStatus table. Although this enforces the status values like ENUM or a check constraint, there are also several ways this solution offers more flexibility.

Querying the set of values#

The set of permitted values is now stored in data, not metadata, as it was with the ENUM data type. We can query data values from a lookup table with SELECT, just like any other table. This makes it much easier to retrieve the set of values as a data set to present in our user interface. We can even sort the set of values the user can choose from.

Querying the set of values

Updating the values in the lookup table#

When we use a lookup table, we can add a value to the set with an ordinary INSERT statement. We can make a change like this without interrupting access to the table. We don’t need to redefine any columns, schedule a downtime, or perform an ETL operation. We also don’t need to know the current set of values in the lookup table to add or remove a value.

Inserting the values in the lookup table

Let’s see what happens when we press “RUN” to execute the code written in the following playground to retrieve the content in the BugStatus table.

Retrieving the values in the BugStatus table

We can also rename a value easily if we declare the foreign key with the ON UPDATE CASCADE option.

Updating the values in the lookup table

Let’s run the code in the following widget to see the output.

Retrieving the BugStatus table after updating the content

We did not have BOGUS as a value in the BugStatus table. Instead, we first added it and then changed its value.

Supporting obsolete values#

We can’t DELETE a row from the lookup table if it’s referenced by a row in Bugs. The foreign key on the status column enforces referential integrity, so the value must exist in the lookup table.

However, we can add another attribute column to the lookup table to designate some values as obsolete. This allows us to maintain historical data in the Bugs.status column while distinguishing between the obsolete values and values that are eligible to appear in our user interface.

Adding a new column for active/inactive status in the status column

We use UPDATE instead of DELETE to make a value obsolete:

Using Update keyword instead of Delete

Then, when we retrieve the set of values to show in a user interface for users to pick, we restrict the query to status values that are ACTIVE:

Querying the values in the lookup table

This gives us more flexibility than an ENUM or a check constraint because those solutions don’t support extra attributes per value.

Portability is easy#

Unlike the ENUM data type, check constraints, domains, or UDTs, the lookup table solution relies only on the standard SQL feature of declarative referential integrity using foreign key constraints. This makes the solution more portable.

We can also keep a virtually unlimited number of values in our lookup table since we store each value on a separate row.

Antipattern: Specify Values in the Column Definition
Synopsis: Phantom Files
Mark as Completed
Report an Issue